feat(rocm): V1 sampling ops for Hermes EngineCore - #234
Conversation
TESTERS WANTED — BF16 / FP16 Gemma-4 MoE on ROCmLab proof for this PR was FP8 Gemma-4-26B-A4B MoE on 2× Radeon AI PRO R9700 (gfx1201). The code path here is dtype-agnostic (V1 sampler ops + Hermes
Please test if you have
Minimal matrix
Report back
Related: serve KV fail-fast #227; FP8 MoE prefill/resident package #228 (FP8-proven; MoE structure notes there). Thanks — BF16/FP16 MoE ROCm coverage has to come from the community; FP8 is what we can gate on RDNA4 consumer hardware day-to-day. |
localai-org-maint-bot
left a comment
There was a problem hiding this comment.
The ROCm coverage is valuable, but this adds/registers ten sampling operations without automated correctness coverage. Please add CPU/CUDA-vs-ROCm parity tests for the new sampler surface, including allowed-token masks, temperature/top-k/top-p, min-p, penalties/logit bias, and deterministic sampling seeds. The current documentation-checkpoint and agent-record failures also need to be resolved before merge.
Addressing CHANGES_REQUESTED (localai-org-maint-bot)Automated sampler coverageAdded CPU↔ROCm parity cases in
Lab run on this box (HIP build, dual R9700): CPU CI still exercises the existing hand-oracle sampler suite; ROCm block is opt-in like the CUDA parity block. Docs gates
Merge order (unchanged)Land #234 first → rebase #227 / #228. Happy to add CUDA-style large-vocab ROCm greedy case or penalty/logit-bias matrix next if you want a wider surface before merge. |
a986e81 to
4198bb5
Compare
|
Fixed agent-record: FEATURES.md Gemma4 ROCm cell was 258 chars (limit 220). Shortened; |
|
Verified on CUDA and it's clean there — one substantive change requested before I merge. Verified for you (GB10, sm_121a, rebased on current
|
…stant Three open PRs each grew their own workaround for the same defect, with three different answers: #227 clamps to 16, #234 and #228 to 4096. All three would conflict with each other, and all three truncate. This fixes it once, in the one place that owns the translation. Hermes and some OpenAI clients send `max_tokens: -1` for "no client-side limit". `SamplingParams::PostInit` rejects anything below 1, which is what made a clamp look necessary — but a constant is the wrong answer, because "unlimited" already has a correct representation here: UNSET. The engine then generates to `max_model_len - seq_len` (`v1/engine/input_processor.cpp:175-180`, a 1:1 mirror of vllm `input_processor.py:317-321`), which is exactly what the client asked for. With a constant, a client asking for unlimited against a 32k model gets 4096 (or 16) tokens back with `finish_reason=length`, indistinguishable from a real limit it set itself. It also hardcodes a policy number into the protocol layer, where `VT_SERVER_MAX_NEW_TOKENS` is already the tunable. Both request paths are covered: `/v1/completions` (`max_tokens`) and `/v1/chat/completions` (`max_completion_tokens` preferred over `max_tokens`, matching chat_completion/serving.py:299). A positive value is honoured unchanged, and a serving-resolved default still wins over an unset request value. RED before, GREEN after, same test and command, on dgx with the new test built against UNPATCHED main: before: test case THREW exception: max_tokens must be at least 1, got -1 29 cases | 28 passed | 1 failed; 175 assertions | 2 failed after: 29 cases | 29 passed | 0 failed; 179 assertions | 0 failed `test_openai_protocol` gains six subcases pinning the semantics: -1 and 0 unset on both paths, a positive value unchanged, and -1 yielding to a serving default when one is supplied. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode]
…ans UNSET Three open PRs each grew a workaround for the same defect with three different answers: #227 clamps to 16, #234 and #228 to 4096. They would conflict with each other and all three truncate. Fixed once, in the layer that owns the translation, so those PRs can drop it and rebase. Hermes and some OpenAI clients send max_tokens=-1 for "no client-side limit"; PostInit rejects <1, which is what made a clamp look necessary. But "unlimited" already has a correct representation: UNSET, which routes to max_model_len - seq_len (input_processor.cpp:175-180, mirroring vllm input_processor.py:317-321). A constant silently truncates exactly the long-context request that asked to be left unlimited. RED before / GREEN after on dgx, the new test built against UNPATCHED main: before: THREW 'max_tokens must be at least 1, got -1'; 28/29 cases, 2 failed after: 29/29 cases, 179/179 assertions Covers both request paths and documents the semantics in docs/USAGE.md. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode]
|
Heads-up: the A non-positive RED before / GREEN after on dgx, the new test built against unpatched main:
Everything else in your PR stands — this is only about the shared hunk. |
60db9a2 to
42ba78e
Compare
Rebase + drop max_tokens clamp (addressing CHANGES_REQUESTED / main drift)Rebuilt tip-of-main ( Dropped (now on main)
Kept (this PR only)
Local gates (on this tip)
Please re-run CI on the force-pushed head. Happy to split further if review still wants sampler kernels alone vs docs. |
CMake already listed rocm_sample.hip (V1 sampler) but the TU was missing from the tree — break configure. Add the file matching mudler#234 tip. PREFILL_GEMM_M env clamp 2048→8192 for lab A/B; default stays 2048. Hot-expert sizes at ~11k already fit 2048 (4096/8192 measured flat). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
42ba78e to
8ea62b2
Compare
|
Rebased onto latest |
|
Merged. This closes a real bug: ROCm registered no sampling ops at all, so EngineCore fatally hit "no kernel for op" the moment prefill finished on AMD. The full V1 surface is now registered for Your red Since no CI job compiles ROCm — there are no AMD runners — the green checks are orthogonal either way, so I verified the port mechanically instead of by reading it. I normalized The numerics are identical, including every piece that decides token identity:
That's a genuine 1:1 port rather than a reimplementation, which is what this project's porting rule asks for and what makes it reviewable without an AMD box. One thing I reviewed and deliberately accepted as-is: Tests skip cleanly via Separately — I've left detailed review notes on #228 asking for that one to be split rather than merged. Different situation entirely; this one was clean. |
Tip-of-main rebuild (back on track)Rebuilt as single commit on current
Please re-run CI. Prior red agent-record/pr-size were base-not-ancestor. |
8ea62b2 to
a418791
Compare
a418791 to
0ed9650
Compare
ROCm/Gemma4 half of the mudler#228 split. No OpenAI SSE/serving changes (see mudler#316). No V1 sampler registration (see mudler#234). Includes dual-GPU FP8 resident experts, peer mix helpers, SharedK-WMMA prefill, decode KV-split path, fused_ops seam. Review fixes absorbed in this tip: - Restore HIPBLAS_V2 (ROCm 6.x mudler#201/mudler#132) — was clobbered in megapr - Do NOT flip platforms/rocm support_static_graph_mode (stays main) - Host-side #if VT_ROCWMMA_OK around SharedK-WMMA launches (no empty kernel launch on non-gfx1200/1201 slices) Still follow-ups (called out in PR body): HIP guards in models/, MoE policy FreeBytes/FP8_NATIVE defaults, peer weight_id / hscale, tests/spec. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
0ed9650 to
f82b26b
Compare
ROCm/Gemma4 half of the mudler#228 split. No OpenAI SSE/serving changes (see mudler#316). No V1 sampler registration (see mudler#234). Includes dual-GPU FP8 resident experts, peer mix helpers, SharedK-WMMA prefill, decode KV-split path, fused_ops seam. Review fixes absorbed in this tip: - Restore HIPBLAS_V2 (ROCm 6.x mudler#201/mudler#132) — was clobbered in megapr - Do NOT flip platforms/rocm support_static_graph_mode (stays main) - Host-side #if VT_ROCWMMA_OK around SharedK-WMMA launches (no empty kernel launch on non-gfx1200/1201 slices) Still follow-ups (called out in PR body): HIP guards in models/, MoE policy FreeBytes/FP8_NATIVE defaults, peer weight_id / hscale, tests/spec. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
f82b26b to
a1adc5e
Compare
ROCm/Gemma4 half of the mudler#228 split. No OpenAI SSE/serving changes (see mudler#316). No V1 sampler registration (see mudler#234). Includes dual-GPU FP8 resident experts, peer mix helpers, SharedK-WMMA prefill, decode KV-split path, fused_ops seam. Review fixes absorbed in this tip: - Restore HIPBLAS_V2 (ROCm 6.x mudler#201/mudler#132) — was clobbered in megapr - Do NOT flip platforms/rocm support_static_graph_mode (stays main) - Host-side #if VT_ROCWMMA_OK around SharedK-WMMA launches (no empty kernel launch on non-gfx1200/1201 slices) Still follow-ups (called out in PR body): HIP guards in models/, MoE policy FreeBytes/FP8_NATIVE defaults, peer weight_id / hscale, tests/spec. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
Register temperature/top-k/top-p/min-p/penalties/masks/logprobs/random sample on ROCm so EngineCore no longer fatals with missing sample ops after prefill. RandomSample is a parallel gumbel-max block reduce (kBlock threads) — the previous 1-thread full-vocab scan collapsed temp>0 decode to ~1.8 t/s on Gemma-4 (V=262k) while greedy stayed ~45. Rebased onto current main; docs are additive only. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
a1adc5e to
b067cd3
Compare
ROCm/Gemma4 half of the mudler#228 split. No OpenAI SSE/serving changes (see mudler#316). No V1 sampler registration (see mudler#234). Includes dual-GPU FP8 resident experts, peer mix helpers, SharedK-WMMA prefill, decode KV-split path, fused_ops seam. Review fixes absorbed in this tip: - Restore HIPBLAS_V2 (ROCm 6.x mudler#201/mudler#132) — was clobbered in megapr - Do NOT flip platforms/rocm support_static_graph_mode (stays main) - Host-side #if VT_ROCWMMA_OK around SharedK-WMMA launches (no empty kernel launch on non-gfx1200/1201 slices) Still follow-ups (called out in PR body): HIP guards in models/, MoE policy FreeBytes/FP8_NATIVE defaults, peer weight_id / hscale, tests/spec. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
Lab live :8010 stack. Serve/parser portion also filed as mudler mudler#328. RandomSample parallel already on mudler#234; keep local park in sync. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Hermes:grok-4.5 [Hermes]
|
@localai-org-maint-bot Status check against CHANGES_REQUESTED / localai-bot notes:
Sanitize ASan/TSan remain ambient-main class. Product jobs (build-test-cpu, cuda-fat, vulkan) were green on the last full run. |
… ROCm (#234) External contribution from Don Mirror (@bakon11). Lab evidence: dual R9700 gfx1201. The bug it closes: ROCm registered no sampling ops at all, so EngineCore fatally hit "no kernel for op" immediately after prefill on AMD. Every V1 sampler op is now registered for DeviceType::kROCM -- temperature, top-k/top-p, probs, logprobs, random sample, penalties, min-p, logit bias, allowed-token-ids and the sparse bad-words token mask. The PR's red agent-record and pr-size checks were the same FORK ARTIFACT as #283: both refused with "base must be an ancestor of head" because the branch was never rebased, so neither could compute a range. sanitize-cpu (address,undefined) is the pre-existing main baseline. No substantive job was red, and no CI job compiles ROCm at all (there are no AMD runners), so the green checks are orthogonal either way. Because CI cannot compile or run this, I verified the port mechanically rather than by reading it. src/vt/rocm/rocm_sample.hip was normalized against src/vt/cuda/cuda_sample.cu (namespace, hip/cuda prefixes, kernel suffixes and whitespace folded away) and every remaining difference is cosmetic: entry-point names, error-string prefixes, line wrapping, and CUDA's trailing explanatory comments. The numerics are IDENTICAL, including the pieces that decide token identity -- SplitMix64, ExpNoise's (r >> 11) + 1 over 9007199254740993.0 mantissa construction, the gumbel/exp-noise argmax, the temperature's !all_random && t < kSamplingEps guard, and flashinfer's two-pivot sort-free bracket search with the same kThreshMaxIter = 64 and the same min_gt_low / max_le_high snapping. This is a genuine 1:1 port, not a reimplementation, which is what the porting rule requires. Reviewed and accepted as-is: RandomSampleK launches <<<n, 1>>>, one thread per row scanning the whole vocab serially. That is not a defect here -- cuda_sample.cu launches it exactly the same way, so the port is faithful and a change would be a divergence. It is a real optimization target for whoever takes ROCm sampling past correctness-grade. Tests skip cleanly via HasRocm() when no ROCm backend is registered, so they are inert on CPU CI and assert real CPU-vs-ROCm parity on AMD hardware. docs/USAGE.md's non-positive max_tokens claim is backed by code already on main (protocol.cpp:525), not by anything this PR asserts without landing. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode]
kCausalConv1dFwd / kCausalConv1dUpdate for ROCm (rocm_gdn_conv.hip), family 2 of the gfx1100 GDN slice, hand-translated from the CUDA donors. Conflicts resolved by union, all additive-vs-additive: the ROCm source list in CMakeLists.txt gained rocm_sample.hip (#234, already on main) and rocm_gdn_state.hip (#334) independently, and both the FEATURES ROCm row and the coordination claim table are keyed records where the two sides touch different keys. Refs #336, #41. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode]
|
Landed on main as Closing manually: it landed as a rebased copy of this branch, so the head SHA here never became reachable from main and GitHub could not auto-close it. Nothing is outstanding; the PR was just left showing OPEN. Thanks for the contribution. |
Summary
Makes Hermes Agent ↔ local Gemma-4-26B MoE FP8 on AMD ROCm (RDNA4) finish chat turns and decode at usable speed when
temperature > 0.Public shout-out context: @anothervariable — Gemma 4 26B MoE on AMD ROCm via vllm.cpp.
Root causes (lab)
1) Missing ROCm V1 sampler ops (engine death)
After a large Hermes SOUL prefill (~38k tokens), sampling called V1 ops registered on CUDA/CPU only:
kApplyAllowedTokenIds(40)no kernel for op 40 on device type 5kApplyTemperature(30)no kernel for op 30request submitted to a stopped AsyncLLMROCm device type = 5. Missing registration = hard engine death, not a soft 4xx.
2)
RandomSampleKserial full-vocab scan (decode cliff)HIP port initially matched CUDA's
<<<n, 1>>>gumbel-max. On gfx1201 with Gemma-4 V=262144, every temp>0 token paid ~0.5s in sampling → Hermes decode ~1.75 t/s vs greedy ~40–50 t/s.Fix: parallel block reduce, launch
<<<n, kBlock>>>withkBlock=256.Changes
src/vt/rocm/rocm_sample.hip— HIP V1 sampler surface:rocm_ops.hip/rocm_dense_basic.hip— register those +ApplyAllowedTokenIds/ApplyTokenMaskmax_tokens=-1(Hermes “unlimited”) → map toVT_SERVER_MAX_NEW_TOKENSdefault (4096) beforeSamplingParams::PostInitrejects<1ENVIRONMENT.md,FEATURES.md,USAGE.md(sampler note)No
vt::rocmcalls frommodels/(device-leakage clean). CUDA still has 1-threadRandomSample(hidden on fat SMs / smaller V); ROCm deliberately diverges for gfx1201.Lab verification (2× Radeon AI PRO R9700, gfx1201, ROCm 7.2)
Also: title-gen allowlist + temp/top_p +
max_tokens=-1→ 200; Paris smoke OK.Binary: CMake
OUTPUT_NAMEisvllm-serverunderbuild-*/examples/.Land order
Follow-ups (not this PR)
cuda_sample.cu)Test plan
python3 scripts/check-device-leakage.pypython3 scripts/check-env-doc.pypython3 scripts/check-doc-checkpoint.py --base origin/main --head HEADpython3 scripts/check-pr-size.py(under budget):8010Gemma-4-26B-A4B-it-fp8 dual-GPU: temp 0 vs 0.7 A/B